package de.wepner.texteditor.ui;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

import javax.swing.AbstractButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.WindowConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import de.wepner.texteditor.model.CommandMapping;
import de.wepner.texteditor.model.EditedFile;
import de.wepner.texteditor.model.EditorData;
import de.wepner.texteditor.model.event.EventListener;


public class EditorWindow extends JFrame {
    
    /** serialVersionUID */
    private static final long serialVersionUID = 1L;
    
    private static final String WINDOW_TITLE = "MVC-Texteditor";
    
    private JMenuBar menu;
    private JMenu fileMenu;
    private JMenuItem newFileMenuItem;
    private JMenuItem loadFileMenuItem;
    private JMenuItem saveFileMenuItem;
    private JMenuItem saveAsFileMenuItem;
    private JMenuItem exitMenuItem;
    
    private JMenu editMenu;
    private JMenuItem undoMenuItem;
    private JMenuItem redoMenuItem;
    
    private JMenu helpMenu;
    private JMenuItem aboutMenuItem;
    
    
    private JScrollPane textFieldScrollPane;
    private JTextArea textField;
    
    
    final EditorData data;
    
    
    public EditorWindow(EditorData data) {
        this.data = data;
        this.data.commands.onCommandChanged.addListener(this.new CommandChangeHandler());
        
        this.setJMenuBar(this.getMenu());
        this.add(this.getTextFieldScrollPane());
        
        this.refreshTitle();
        this.setMinimumSize(new Dimension(320, 240));
        
        this.data.onEditedFileChanged.addListener(new EditedFileChangedHandler());
        this.addWindowListener(this.new EditorWindowListener());
        this.data.closeEditor.addListener(this.new ExitHandler());
        
        this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
        this.initialiseCommands();
    }
    
    
    private JMenuBar getMenu() {
        if (this.menu == null) {
            this.menu = new JMenuBar();
            this.menu.add(this.getFileMenu());
            this.menu.add(this.getEditMenu());
            this.menu.add(this.getHelpMenu());
        }
        return this.menu;
    }
    
    private JMenu getFileMenu() {
        if (this.fileMenu == null) {
            this.fileMenu = new JMenu("File");
            this.fileMenu.setMnemonic('F');
            this.fileMenu.add(this.getNewFileMenuItem());
            this.fileMenu.add(this.getLoadFileMenuItem());
            this.fileMenu.add(new JSeparator());
            this.fileMenu.add(this.getSaveFileMenuItem());
            this.fileMenu.add(this.getSaveFileAsMenuItem());
            this.fileMenu.add(new JSeparator());
            this.fileMenu.add(this.getExitMenuItem());
        }
        return this.fileMenu;
    }
    
    private JMenuItem getNewFileMenuItem() {
        if (this.newFileMenuItem == null) {
            this.newFileMenuItem = new JMenuItem("New", KeyEvent.VK_N);
            this.newFileMenuItem.setAccelerator(KeyStroke.getKeyStroke("control N"));
        }
        return this.newFileMenuItem;
    }
    
    private JMenuItem getLoadFileMenuItem() {
        if (this.loadFileMenuItem == null) {
            this.loadFileMenuItem = new JMenuItem("Open...", KeyEvent.VK_O);
            this.loadFileMenuItem.setAccelerator(KeyStroke.getKeyStroke("control O"));
        }
        return this.loadFileMenuItem;
    }
    
    private JMenuItem getSaveFileMenuItem() {
        if (this.saveFileMenuItem == null) {
            this.saveFileMenuItem = new JMenuItem("Save", KeyEvent.VK_S);
            this.saveFileMenuItem.setAccelerator(KeyStroke.getKeyStroke("control S"));
        }
        return this.saveFileMenuItem;
    }
    
    private JMenuItem getSaveFileAsMenuItem() {
        if (this.saveAsFileMenuItem == null) {
            this.saveAsFileMenuItem = new JMenuItem("Save as...", KeyEvent.VK_A);
        }
        return this.saveAsFileMenuItem;
    }
    
    private JMenuItem getExitMenuItem() {
        if (this.exitMenuItem == null) {
            this.exitMenuItem = new JMenuItem("Exit", KeyEvent.VK_E);
        }
        return this.exitMenuItem;
    }
    
    
    private JMenuItem getEditMenu() {
        if (this.editMenu == null) {
            this.editMenu = new JMenu("Edit");
            this.editMenu.setMnemonic('E');
            this.editMenu.add(this.getUndoMenuItem());
            this.editMenu.add(this.getRedoMenuItem());
        }
        return this.editMenu;
    }
    
    private JMenuItem getRedoMenuItem() {
        if (this.redoMenuItem == null) {
            this.redoMenuItem = new JMenuItem("Redo", KeyEvent.VK_R);
            this.redoMenuItem.setAccelerator(KeyStroke.getKeyStroke("control Y"));
        }
        return this.redoMenuItem;
    }
    
    private JMenuItem getUndoMenuItem() {
        if (this.undoMenuItem == null) {
            this.undoMenuItem = new JMenuItem("Undo", KeyEvent.VK_U);
            this.undoMenuItem.setAccelerator(KeyStroke.getKeyStroke("control Z"));
        }
        return this.undoMenuItem;
    }
    
    
    private JMenu getHelpMenu() {
        if (this.helpMenu == null) {
            this.helpMenu = new JMenu("?");
            this.helpMenu.setMnemonic('?');
            this.helpMenu.add(this.getAboutMenuItem());
        }
        return this.helpMenu;
    }
    
    private JMenuItem getAboutMenuItem() {
        if (this.aboutMenuItem == null) {
            this.aboutMenuItem = new JMenuItem("About", KeyEvent.VK_A);
        }
        return this.aboutMenuItem;
    }
    
    
    private JScrollPane getTextFieldScrollPane() {
        if (this.textFieldScrollPane == null) {
            this.textFieldScrollPane = new JScrollPane(this.getTextField());
            this.textFieldScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
            this.textFieldScrollPane.setBorder(null);
        }
        return this.textFieldScrollPane;
    }
    
    JTextArea getTextField() {
        if (this.textField == null) {
            this.textField = new JTextArea();
            this.textField.getDocument().addDocumentListener(new DocumentListener() {
                
                @Override
                public void removeUpdate(DocumentEvent event) {
                    this.textChanged();
                }
                
                @Override
                public void insertUpdate(DocumentEvent event) {
                    this.textChanged();
                }
                
                @Override
                public void changedUpdate(DocumentEvent event) {
                    this.textChanged();
                }
                
                private void textChanged() {
                    EditorWindow.this.data.getEditedFile().setContent(EditorWindow.this.getTextField().getText());
                }
            });
            this.textField.setEnabled(this.data.getEditedFile() != null);
        }
        return this.textField;
    }
    
    
    void refreshTitle() {
        
        String fileName;
        if (this.data.getEditedFile() != null) {
            fileName = this.data.getEditedFile().getName();
        } else {
            fileName = null;
        }
        this.setTitle((fileName != null ? fileName + (this.data.getEditedFile().isDirty() ? "*" : "") + " - " : "")
                + EditorWindow.WINDOW_TITLE);
    }
    
    
    private void initialiseCommands() {
        for (String key : EditorData.ALL_COMMANDS) {
            this.assignCommand(key, this.data.commands.getCommand(key));
        }
    }
    
    void editedFileChanges(EditedFile newFile) {
        if (newFile != null) {
            EditorWindow.this.getTextField().setText(newFile.getContent());
            EditorWindow.this.getTextField().setEnabled(true);
            newFile.setDirty(false);
            newFile.onDirtyChanged.addListener(this.new EditedFileDirtyChangeHandler());
            newFile.onFileNameChanged.addListener(this.new EditedFileNameChangeHandler());
        } else {
            EditorWindow.this.getTextField().setEnabled(false);
        }
        EditorWindow.this.refreshTitle();
    }
    
    
    void assignCommand(String key, ActionListener command) {
        final AbstractButton button;
        switch (key) {
            case EditorData.NEW_FILE_COMMAND:
                button = this.getNewFileMenuItem();
                break;
            case EditorData.LOAD_FILE_COMMAND:
                button = this.getLoadFileMenuItem();
                break;
            case EditorData.SAVE_FILE_COMMAND:
                button = this.getSaveFileMenuItem();
                break;
            case EditorData.SAVE_FILE_AS_COMMAND:
                button = this.getSaveFileAsMenuItem();
                break;
            case EditorData.EXIT_APPLICATION_COMMAND:
                button = this.getExitMenuItem();
                break;
            case EditorData.UNDO_COMMAND:
                button = this.getUndoMenuItem();
                break;
            case EditorData.REDO_COMMAND:
                button = this.getRedoMenuItem();
                break;
            case EditorData.ABOUT_COMMAND:
                button = this.getAboutMenuItem();
                break;
            default:
                button = null;
                break;
        }
        
        if (button != null) {
            final ActionListener[] listeners = button.getActionListeners();
            for (ActionListener listener : listeners) {
                button.removeActionListener(listener);
            }
            button.addActionListener(command);
        }
    }
    
    
    private class CommandChangeHandler implements EventListener<CommandMapping, CommandMapping.CommandChangeArguments> {
        
        public CommandChangeHandler() {
            
        }
        
        
        @Override
        public void eventOccurred(CommandMapping self, CommandMapping.CommandChangeArguments param) {
            EditorWindow.this.assignCommand(param.key, param.newCommand);
        }
    }
    
    private class EditorWindowListener implements WindowListener {
        
        public EditorWindowListener() {
            
        }
        
        
        @Override
        public void windowOpened(WindowEvent e) {
            
        }
        
        @Override
        public void windowClosing(WindowEvent e) {
            ActionEvent eventData = new ActionEvent(EditorWindow.this, 0, "");
            EditorWindow.this.data.commands.getCommand(EditorData.EXIT_APPLICATION_COMMAND).actionPerformed(eventData);
        }
        
        @Override
        public void windowClosed(WindowEvent e) {
            
        }
        
        @Override
        public void windowIconified(WindowEvent e) {
            
        }
        
        @Override
        public void windowDeiconified(WindowEvent e) {
            
        }
        
        @Override
        public void windowActivated(WindowEvent e) {
            
        }
        
        @Override
        public void windowDeactivated(WindowEvent e) {
            
        }
    }
    
    private class EditedFileChangedHandler implements EventListener<EditorData, EditedFile> {
        
        public EditedFileChangedHandler() {
            
        }
        
        @Override
        public void eventOccurred(EditorData self, EditedFile param) {
            editedFileChanges(param);
        }
    }
    
    private class EditedFileDirtyChangeHandler implements EventListener<EditedFile, Boolean> {
        
        public EditedFileDirtyChangeHandler() {
            
        }
        
        @Override
        public void eventOccurred(EditedFile self, Boolean param) {
            EditorWindow.this.refreshTitle();
        }
    }
    
    private class EditedFileNameChangeHandler implements EventListener<EditedFile, String> {
        
        public EditedFileNameChangeHandler() {
            
        }
        
        @Override
        public void eventOccurred(EditedFile self, String param) {
            EditorWindow.this.refreshTitle();
        }
    }
    
    private class ExitHandler implements EventListener<EditorData, Integer> {
        
        public ExitHandler() {
            
        }
        
        
        @Override
        public void eventOccurred(EditorData self, Integer param) {
            EditorWindow.this.dispose();
        }
    }
}
